perm filename DEBUG.MEM[PRO,SYS] blob sn#601367 filedate 1981-07-29 generic text, type T, neo UTF8

                         PROLOG DEBUGGING FACILITIES
                         PROLOG DEBUGGING FACILITIES
                         PROLOG DEBUGGING FACILITIES
                         PROLOG DEBUGGING FACILITIES
                         PROLOG DEBUGGING FACILITIES


                                Lawrence Byrd


  In  this  paper  we  shall  describe  the  new debugging facilities that are
available in the Prolog interpreter.  The  purpose  of  these  facilities,  as
compared with the previous versions, is to provide more information concerning
the  control  flow of your program, and to allow a greater choice of when this
should occur.  The main features of the debugging package are as follows:


   - The 'text processor' model of  Prolog  execution  which  provides  a
     simple   way   of   visualising   control  flow,  especially  during
     backtracking.

   - The ability to selectively set spy-points, or to  exaustively  trace
     your program.

   - The  wide choice of control and information options available during
     debugging.


We shall now go on to describe each of these in turn.



1. Control Flow Model
1. Control Flow Model
1. Control Flow Model
1. Control Flow Model
1. Control Flow Model

  During debugging the interpreter prints out a sequence of goals  in  various
states  of instantiation in order to show where the program has reached in its
execution.  However, in order to understand what is occuring it  is  important
to also understand when and why the interpreter prints out goals.  In a normal
programming language the key points of interest are function entry and return.
But  Prolog is a non-determinate language and this introduces the complexities
of backtracking.  Not only are clauses entered and left but  backtracking  can
suddenly  re-activate  them in order to produce an alternative result.  One of
the major confusions that novice  Prolog  programmers  have  to  face  is  the
question  of  what  actually  occurs when a goal fails and the system suddenly
starts backtracking.  I shall now describe a model of Prolog  execution  which
views  the  program  control flow in terms of movement about the program text.
This model provides a basis for the debugging  mechanism  in  the  interpreter
and,  it  is hoped, enables the user to view the behaviour of his program in a
consistent way.

  Let us look at an example Prolog procedure :
                                      2


             *-----------------------------*
     CALL    |                             |    EXIT
  ---------> +   descendant(X,Y)           + --------->
             |       :- offspring(X,Y).    |
             |                             |
             |   descendant(X,Z)           |
             |       :- offspring(X,Y),    |
  <--------- +          descendant(Y,Z).   + <---------
     FAIL    |                             |    REDO
             *-----------------------------*



  This  is part of the simple database example given in the User's Guide.  The
first clause states that Y is a descendant of X if Y is an offspring of X, and
the second clause states that Z is a descendant X if Y is an  offspring  of  X
and  if  Z  is a descendant of Y. In the diagram I have drawn a box around the
whole procedure and added some labelled arrows which indicate the control flow
in and out of the box.  There are four such arrows which we shall look  at  in
turn.


Call            This  arrow  represents  initial  invocation of the procedure.
                When a goal of the form 'descendant(X,Y)' is  required  to  be
                                                                 port         
                                                                 port         
                                                                 port         
                                                                 port         
                satisfied,  control  passes  through  the  call  port  of  the
                           box                                                
                           box                                                
                           box                                                
                           box                                                
                descendant box with the  intention  of  matching  a  component
                clause  and  then  satisfying any subgoals in the body of that
                clause.  Notice that this is independent  of  whether  such  a
                match  is  possible;  i.e.  the  box  is called, and then such
                matters are worried about.  Textually we  can  imagine  moving
                our  finger  to the code for descendant when meeting a call to
                descendant in some other part of the code.

Exit            This arrow represents a successful return from the  procedure.
                This occurs when the initial goal has been unified with one of
                the  component  clauses  and any subgoals have been satisfied.
                                                   port                   box 
                                                   port                   box 
                                                   port                   box 
                                                   port                   box 
                Control now passes out of the exit port of the descendant box.
                Textually we stop following the code  for  descendant  and  go
                back to the place we came from.

Redo            This  arrow  indicates  that  a subsequent goal has failed and
                that  the  system  is  backtracking  in  an  attempt  to  find
                alternatives  to  previous  solutions.  Control passes through
                         port                   box                           
                         port                   box                           
                         port                   box                           
                         port                   box                           
                the redo port of the descendant box.  An attempt will  now  be
                made to resatisfy one of the component subgoals in the body of
                the  clause  that  last  succeeded;  or,  if  that  fails,  to
                completely rematch  the  original  goal  with  an  alternative
                clause  and  then  try  to satisfy any subgoals in the body of
                                                                 backwards    
                                                                 backwards    
                                                                 backwards    
                                                                 backwards    
                this new clause.  Textually we follow the  code  backwards  up
                the  way  we came looking for new ways of succeeding, possibly
                dropping down on to  another  clause  and  following  that  if
                                      3

                necessary.

Fail            This  arrow  represents  a  failure of the initial goal, which
                might occur if no clause if matched, or if subgoals are  never
                satsified,  or  if any solution produced is always rejected by
                                                                      port    
                                                                      port    
                                                                      port    
                                                                      port    
                later processing.  Control now passes out of the fail port  of
                                 box                                          
                                 box                                          
                                 box                                          
                                 box                                          
                the  descendant  box  and  the systems continues to backtrack.
                Textually we move our finger back to  the  code  which  called
                this  procedure  and keep moving backwards up the code looking
                for choice points.


  In terms of this model, the information we get about the  procedure  box  is
only the control flow through these four ports.  This means that at this level
we  are  not  concerned  with  which  clause matches, and how any subgoals are
satisfied, but rather we only wish to know the  initial  goal  and  the  final
outcome.    However,  it  can  be  seen that whenever we are trying to satisfy
                                                                         their
                                                                         their
                                                                         their
                                                                         their
subgoals, what we are actually doing is passing through  the  ports  of  their
respective  boxes.    If  we  were to follow this, then we would have complete
                                   inside                                     
                                   inside                                     
                                   inside                                     
                                   inside                                     
information about the control flow inside the descendant  procedure  box.    I
shall  go on to consider an example, but first I should point out that the box
                                                              invocation  box 
                                                              invocation  box 
                                                              invocation  box 
                                                              invocation  box 
we have drawn round the procedure should really be seen as an invocation  box.
That  is to say, we will have a different box for each different invocation of
the procedure.  Obviously, with something like a recursive procedure,  we  are
going  to have lots of different Calls and Exits in the control flow but these
will be for different invocations.  Since this might get  confusing  we  shall
          invocation box                             
          invocation box                             
          invocation box                             
          invocation box                             
give each invocation box a unique integer identifier.

  Let  us now take a look at an example.  I shall use the descendant procedure
we have been looking at, and we shall follow the control flow through all  the
ports  of  this  procedure  and  of  the  'offsping'  procedure.  First let me
reproduce the code :
                                      4


            *------------------------------*
    CALL    |                              |    EXIT
 ---------> +   descendant(X,Y)            + --------->
            |                              |
            |       *-----------------*    |
            |   :-  | offspring(X,Y). |    |
            |       *-----------------*    |
            |                              |
            |   descendant(X,Z)            |
            |                              |
            |       *-----------------*    |
            |   :-  | offspring(X,Y), |    |
            |       *---+---------+---*    |
            |           |         |        |
            |       *---+---------+----*   |
            |       | descendant(Y,Z). |   |
            |       *---+---------+----*   |
            |           |         |        |
 <--------- +           *->       *-<      + <---------
    FAIL    |                              |    REDO
            *------------------------------*



            *----------------------------------*
    CALL    |                                  |    EXIT
 ---------> +    offspring(abraham,ishmael).   + --------->
            |                                  |
            |    offspring(abraham,isaac).     |
            |                                  |
            |    offspring(isaac,esau).        |
            |                                  |
 <--------- +    offspring(isaac,jacob).       + <---------
    FAIL    |                                  |    REDO
            *----------------------------------*



  It is important that you try and follow the trace we are about to look at by
physically  tracing  along  the  arrows  entering and leaving the above boxes.
This will show how it is possible to follow the execution in terms of  up  and
down  movement  in  the program text.  Backtracking is followed by moving your
            up                                                   redo         
            up                                                   redo         
            up                                                   redo         
            up                                                   redo         
finger back up the code, every goal you meet can be seen  as  a  redo  in  the
                                                                 exit         
                                                                 exit         
                                                                 exit         
                                                                 exit         
trace.    If  this  goal  succeeds again then we come out at the exit port and
travel down the text as normal, reexecuting all the following goals  (see  the
     call                                                   fail              
     call                                                   fail              
     call                                                   fail              
     call                                                   fail              
new  calls etc).  If the goal fails then we come out at the fail port and keep
going backwards looking for a choice point.  The fact that  we  are  following
all  the previously executed goals back the way we came, means that it is very
easy to see where the choice point is found.

  The following goal is followed by a 'fail'.  The purpose of this is to force
                                      5

all  possible  backtracking  behaviour  out  of the descendant procedure.  The
command as a whole can therefore never succeed.  However, the  point  of  this
trace  is  to  observe the execution flow induced by the failure of the second
goal ('fail').  Let us assume, then, that we start with the command:



             *--------------------------*     *--------*
             |                          | --> |        |
        ?-   | descendant(abraham,ANS), |     |  fail. |
             |                          | <-- |        |
             *--------------------------*     *--------*



                                   Control first passes through the call  port
                                of descendant


   (1) Call : descendant(abraham,ANS)


                                   I  have  given  this  invocation  a  unique
                                number and I shall hang  on  to  the  original
                                variable names for clarity.


   (2) Call : offspring(abraham,ANS)


                                   Obviously  we have matched the first clause
                                of the descendant procedure and  this  results
                                in  the  control flow passing through the call
                                port of offspring.


   (2) Exit : offspring(abraham,ishmael)


                                   Immediate success on the first  clause  and
                                control passes out through the exit port.


   (1) Exit : descendant(abraham,ishmael)


                                   And   thus  we  have  satisfied  the  first
                                descendant clause.


   (3) Call : fail
   (3) Fail : fail
                                      6

   (1) Redo : descendant(abraham,ishmael)


                                   Then we call fail and, as might be expected
                                this  fails  !. Control passes out of the fail
                                port of fail  and  we  are  now  backtracking,
                                moving  in the opposite direction from before,
                                looking  for  alternatives.    When  we   redo
                                descendant we are in exactly the same place as
                                when  we  left  except  for  the  direction of
                                movement. Hence the instantiation state of the
                                goal.  If you wish to know the original  state
                                of  the  goal, then look back to the Call port
                                corresponding to this invocation number.


   (2) Redo : offspring(abraham,ishmael)
   (2) Exit : offspring(abraham,isaac)


                                   We redo offspring and drop  down  onto  the
                                next  clause  thus  producing  an  alternative
                                solution.


   (1) Exit : descendant(abraham,isaac)
   (4) Call : fail
   (4) Fail : fail
   (1) Redo : descendant(abraham,isaac)


                                   Again  fail  causes  us  to   reject   this
                                solution  and  to  start backtracking.  Notice
                                that this was a completely new  invocation  of
                                fail.


   (2) Redo : offspring(abraham,isaac)
   (2) Fail : offspring(abraham,ANS)


                                   This   time,   offspring  cannot  offer  us
                                another match and so we continue  backtracking
                                with  control  passing  out  through  the fail
                                port.


   (5) Call : offspring(abraham,Y)


                                   What has happened  here  is  that  we  have
                                dropped down onto the second descendant clause
                                      7

                                and   this   is  a  completely  new  offspring
                                invocation corresponding to the first subgoal.


   (5) Exit : offspring(abraham,ishmael)
   (6) Call : descendant(ishmael,ANS)


                                   This provides a solution with which we  now
                                recursively  call descendant.  This gives us a
                                new invocation of descendant.


   (7) Call : offspring(ishmael,ANS)
   (7) Fail : offspring(ishmael,ANS)
   (8) Call : offspring(ishmael,Y2)
   (8) Fail : offspring(ishmael,Y2)
   (6) Fail : descendant(ishmael,ANS)


                                   Ishmael has no offspring (in this example),
                                and so the offspring subgoals in both ancestor
                                clauses fail thus failing the descendant goal.


   (5) Redo : offspring(abraham,ishmael)


                                   Back we go for an alternative.


   (5) Exit : offspring(abraham,isaac)
   (9) Call : descendant(isaac,ANS)
   (10) Call : offspring(isaac,ANS)
   (10) Exit : offspring(isaac,esau)


                                   We get a new invocation of  descendant  and
                                the offspring subgoal succeeds.


   (9) Exit : descendant(isaac,esau)
   (1) Exit : descendant(abraham,esau)
   (11) Call : fail
   (11) Fail : fail
   (1) Redo : descendant(isaac,esau)
   (9) Redo : descendant(isaac,esau)


                                   This  provides  a  final  solution  to  the
                                initial ancestor  goal  but  the  fail  forces
                                backtracking  again  and so back we come along
                                      8

                                the redo paths.


   (10) Redo : offspring(isaac,esau)
   (10) Exit : offspring(isaac,jacob)
   (9) Exit : descendant(isaac,jacob)
   (1) Exit : descendant(abraham,jacob)


                                   The    offspring    subgoal   has   another
                                alternative which produces another result  for
                                the  initial  descendant goal. As can be seen,
                                this is abraham's  last  possible  descendant,
                                however there is a certain amount of work left
                                to  be  done.  Let  us  continue to follow the
                                control flow  as  it  backtracks  unsucessfuly
                                back to the begining.


   (12) Call : fail
   (12) Fail : fail
   (1) Redo : descendant(abraham,jacob)
   (9) Redo : descendant(isaac,jacob)
   (10) Redo : offspring(isaac,jacob)
   (10) Fail : offspring(issac,ANS)
   (13) Call : offspring(isaac,Y3)


                                   We  are  now  trying  the second clause for
                                ancestor.


   (13) Exit : offspring(isaac,esau)
   (14) Call : descendant(esau,ANS)


                                   Recurse again.


   (15) Call : offspring(esau,ANS)
   (15) Fail : offspring(esau,ANS)
   (16) Call : offspring(esau,Y4)
   (16) Fail : offspring(esau,Y4)
   (14) Fail : descendant(esau,ANS)
   (13) Redo : offspring(isaac,esau)
   (13) Exit : offspring(isaac,jacob)
   (17) Call : descendant(jacob,ANS)


                                   Try jacob.
                                      9

   (18) Call : offspring(jacob,ANS)
   (18) Fail : offspring(jacob,ANS)
   (19) Call : offspring(jacob,Y5)
   (19) Fail : offspring(jacob,Y5)
   (17) Fail : descendant(jacob,ANS)
   (13) Redo : offspring(isaac,jacob)
   (13) Fail : offspring(isaac,Y3)
   (9) Fail : descendant(isaac,ANS)
   (1) Fail : descendant(abraham,ANS)

 no


  And that's the end of that.  I hope that this exaustive example has provided
an  understanding  of  the  control flow involved in the execution of a Prolog
program.  (Not that you didn't understand  this  already,  of  course).    You
                                                                      call    
                                                                      call    
                                                                      call    
                                                                      call    
should  have noticed that for any invocation there is always only one call and
fail                                        redo                    exit      
fail                                        redo                    exit      
fail                                        redo                    exit      
fail                                        redo                    exit      
fail, although there may be arbitrarly many redos and corresponding exits ( >=
0 ).  It is the initial call which introduces the invocation and  it  is  here
that we first see the new invocation numbers.

  If  you  followed  the  steps  above by tracing along the arrows linking the
boxes in the code then you will have a good idea of the sense in which this is
a 'textual processor' model of Prolog.  The execution of the  program  can  be
directly  followed  in  the  program  text.  (I.e. there is nothing mysterious
going on).  The  'text  processing'  model  can  be  viewed  as  a  particular
traversal  of the and/or tree for the program.  (This is described in my paper
"Understanding the Control Flow of Prolog Programs").  We now have  the  basic
model with which to understand the order of the goals printed by the debugging
facilities.    One  question  that  may  have occurred to you during the above
example is "Do I really have to see ALL that ?!".  The answer is no, and  next
three sections examine how you can control the amount of information presented
to you during debugging.



2. Control of Debugging Facilities
2. Control of Debugging Facilities
2. Control of Debugging Facilities
2. Control of Debugging Facilities
2. Control of Debugging Facilities

  In  order  to  control  the  degugging facilities the interpreter provides a
range of evaluable predicates which enable the user to switch debugging on and
off,  to  set  spy-points  and  to  specify  various  kinds   of   'leashing'.
(Spy-points  are a way of deciding to spy only on a particular procedure.  You
can decide to see control pass through the various ports of such  a  procedure
without  actually  having to exaustively trace your way there.  Spy-points are
similar to breakpoints in other languages).


debug                                                                         
debug                                                                         
debug                                                                         
debug                                                                         
debug           This evaluable predicate switches debugging mode on.   (It  is
                initially  off).   In order for the full range of control flow
                information to be available it is necessary to  have  this  on
                from  the  start.  When it is off the system does not remember
                invocations that are being executed.  (This is because  it  is
                                      10

                expensive  and not required for normal running programs).  You
                can switch debug mode on in the middle  of  execution,  either
                from within your program or after a control-C (see later), but
                information prior to this will just be unavailable.

nodebug                                                                       
nodebug                                                                       
nodebug                                                                       
nodebug                                                                       
nodebug         This switches debug mode off.  If there are any spy-points set
                then they will be removed.

spy X                                                                         
spy X                                                                         
spy X                                                                         
spy X                                                                         
spy X           This evaluable predicate sets spy-points on all the procedures
                given by X. X is either a single predicate specification, or a
                list  of  such  specifications.   A predicate specification is
                either of the form <atom>/<Arity>, which means  the  procedure
                with  the  name  <atom>  and an arity of <Arity> (eg member/2,
                foo/0, hello/27).  Or it can be  of  the  form  <atom>,  which
                means  all  the procedures with the name <atom> that currently
                have clauses in the data-base, ie this  may  specify  multiple
                procedures  which have the same name but different arities (eg
                member, foo, hello).  If you use the form <atom> but there are
                no clauses for this predicate (of any Arity) then nothing will
                be done.  If you really  want  to  place  a  spy  point  on  a
                currently  non-existent  procedure, then you must use the full
                form <atom>/<Arity>.  (You will get a warning message in  this
                case  -  just  so  you  know whats going on).  If you set some
                spy-points  when  debug  mode  is  off   then   it   will   be
                automatically switched on for you.

nospy X                                                                       
nospy X                                                                       
nospy X                                                                       
nospy X                                                                       
nospy X         This  is similar to spy X except that all the procedures given
                by X will have previously set spy-points removed from them.

debugging                                                                     
debugging                                                                     
debugging                                                                     
debugging                                                                     
debugging       This evaluable predicate will print out information  onto  the
                terminal   showing  whether  debug  mode  is  on  or  off  and
                indicating what  spy-points  are  set.    (It  also  specifies
                leashing information (see later)).


  These  conventions  mean that you can be in one of three states at any time.
Either 1) Debug Mode is off, or 2) Debug Mode is  on  although  there  are  no
spy-points set, or 3) Debug Mode is on and there are some spy-points set.

  Setting  a  spy-point  on  a  procedure  indicates  that you wish to see all
control flow through the various ports of its invocation boxes (ie Call, Exit,
Redo and Fail - as explained above).  When control passes through such a  port
(of  a  procedure with a spy-point set on it), a message is output showing the
invocation number, port type, goal instantiation state etc.  (Similar  to  the
example above).  The user is then asked to interact in order to decide what to
do.    I  shall  be going on to discuss the possible options available at this
point, but first I must outline  the  three  principle  actions  that  can  be
performed.

  These  three  actions  are  LEAP,  CREEP  and SKIP.  Each of these should be
considered as a one-off command, they  say  "Do  this  now".    They  are  NOT
switches  which  put you into funny modes.  Whenever you are asked to interact
                                      11

it  is  irrelevant  which action you used the last time - this will be a fresh
decision about what you want to do now.  With that in mind let's look at  each
of them in turn.

  Leap  means  "OK,  Now  just keep going until you find another spy-point and
wake me up then".  I.e. You will see nothing until control  passes  through  a
port  of a procedure with a spy-point set.  Then a message will be printed and
you will be asked to interact again.  Leaping can thus be used to  follow  the
execution  at a higher level than exaustive tracing.  All you need to do is to
set spy-points on an evenly spread  set  of  pertinent  procedures,  and  then
follow the control flow through these by leaping from one to the other.

  Creep,  on  the other hand, means "Hey this looks interesting, I want to see
the very next port that control  passes  through  regardless  of  spy-points".
I.e.  You  will  get  a  message  for the next port and will be asked again to
interact there.  If you keep creeping then you will get an exaustive trace  of
the  whole  execution  (both  forwards  and backwards).  Notice that this will
often involve seeing procedures which do not have spy-points set.  If you Leap
from one of these then, obviously, you won't see any more of  it  (unless  you
creep back to it, or use some magic as described later).

  Finally,  Skip  means  "I'm not too bothered as to what happens in there, so
just do it and don't tell me anything until you get  back".    (Skip  is  only
valid  for call and redo ports).  I.e. You will not see anything until control
comes back to this procedure (this will either be the exit port  or  the  fail
port).    Skip  is  particularly  useful  when  used  at non spy-points (while
creeping) since  it  gaurantees  that  control  will  be  returned  after  the
(possibly  complex)  execution within the box.  If you skip then no message at
all will appear until control returns.  This includes calls to procedures with
spy-points set; they will be masked out during the skip.  There are  two  ways
of  overriding this :  there is a quasi-skip which does not ignore spy-points,
and the 't' option after a Control-C interrupt (see later)  will  disable  the
masking.  Normally, however, this masking is just what is required!

  The basic idea is that, for the most part, one is relying on spy-points that
have  been  set  in advance to capture the overall control flow (using Leap to
watch it all go by).  When something goes wrong, one is then able  to  'single
step' through the program (using Creep) to see what the problem is, using Skip
to  dodge  uninteresing  bits that would take a lot of time.  Since Debug Mode
will have been on all the time, all invocations (barring those that have  been
cut  out  of  the  search  space (using !)), will be available so that one can
Creep ('single step') anywhere; down into places one didn't  look  at  before,
back  past spy-points etc. etc.  One could even start setting extra spy-points
!  Spy-points can be set at any time and they will always be applicable to the
next control movement through their particular procedure.  This means that not
only can spy-points be set up for future calls, but you can set them so as  to
catch  procedures  that  have already been called.  (These will then catch the
Redo's and Fail's that might then occur.)

  For some cases (or more likely, for  those  who  have  obsolete  methods  of
debugging  etched  into  their  bones),  it may be preferable to start 'single
stepping' right from the begining and so one requires a way of making a  Creep
                                      12

decision  at  the start, before the first goal is reached.  This is done using
the following evaluable predicate:


trace                                                                         
trace                                                                         
trace                                                                         
trace                                                                         
trace           This switches Debug Mode on, if it is not on already, and then
                makes a Creep decision so that the next  port  control  passes
                through  will  produce a message and you will then be asked to
                interact.  Note that this is a  once-off  decision,  you  will
                have  to call trace again if you want the same thing to happen
                on your next command.  This is in accordance with the  meaning
                of  Creep given above.  Once you have reached this first port,
                it is then up to you what you do - Leap,  Creep  or  Skip  (or
                some other fancy option as described later).


  You  can  also call trace by responding "t" to the Control-C handler when it
prompts you after an interrupt.  This gives you the ability to start debugging
at any stage by just hitting Control-C,  followed  by  "t  <CR>".    Remember,
though,  that  if  Debug  Mode  was  not  switched on before, there will be no
information about invocations previous to the interrupt.

  The above discussion  has  assumed  that  whenever  you  receive  a  message
indicating  that  control is passing through a particular port, you always get
prompted so that you can interact.   While  this  is  true  initially,  is  is
however  possible  to change it.  The debugging package allows you to vary the
type of 'leashing' that occurs.  If  a  port  is  leashed  then  you  will  be
prompted  and  execution  effectively stops while you decide what to do.  If a
port is unleashed then no prompt is issued  (although  the  trace  message  is
                                                 Creep                        
                                                 Creep                        
                                                 Creep                        
                                                 Creep                        
still  output)  and  execution continues as if a Creep decision had been made.
I.e. The very next port will produce a message and will then stop or  continue
depending  on  whether  it  is  leashed.    If  all  ports  are unleashed then
absolutely everything will  appear  on  your  screen  without  so  much  as  a
by-your-leave.      Leashing,   however,  is  not  applicable  to  spy-points.
Spy-points will always stop, print their message, and prompt  for  interaction
at  all  their ports.  (This is, after all, the point of them).  The following
evaluable predicate will set the leasing mode for all non spy-points:


leash(Mode)                                                                   
leash(Mode)                                                                   
leash(Mode)                                                                   
leash(Mode)                                                                   
leash(Mode)     Leashing mode is set to Mode, where mode can  be  one  of  the
                following:
                   full  - prompt on call, exit, redo and fail
                   tight - prompt on call, redo and fail
                   half  - prompt on call and redo
                   loose - prompt on call
                   off   - never prompt
                or  Mode can be an integer between 0 and 15 which will set any
                arbitrary combination not covered above.  (Treat  the  integer
                as  a  binary  number,  2'abcd,  where  a, b, c and d give the
                yes/no (1/0) decisions for the call, exit, redo and fail ports
                respectively.)
                                      13

  The  initial  value of mode is 'half'.  This provides all the detail without
continuosly prompting at every single port.  If you don't  like  this  default
then you can always reset it, of course.  (You could use a prolog.ini file for
this if you always wanted it set to something else).

  This  completes  this section, but before going on to talk about the message
output at each port and the options available when  you  stop,  I  would  just
remind you that the global debugging state (which determines what goes on) can
always  be  discovered by using the evaluable predicate 'debugging'.  In fact,
all the predicates mentioned above will output  useful  messages  letting  you
know what they are up to.



3. Format of Debugging messages
3. Format of Debugging messages
3. Format of Debugging messages
3. Format of Debugging messages
3. Format of Debugging messages

  We shall now look at the exact format of the message output by the system at
a port.  All trace messages are output to the terminal regardless of where the
current  output  is directed.  (This allows you to successfully trace programs
while they are performing file I/O).  The basic format is as follows:



** (23) 6 Call : foo(hello,there,←123) ?



  The '**' indicates that this is a spy-point.  If this  port  is  not  for  a
procedure  with  a spy-point set, then there will be two spaces there instead.
If this port is the requested return from a Skip  then  the  second  character
becomes '>'.  This gives four possible combinations.


'**'            This is a spy-point.

'*>'            This  is  a  spy-point,  and you also did a Skip last time you
                were in this box.

' >'            This is not a spy-point, but you did a Skip last time you were
                in this box.

'  '            This is not a spy-point.


  The number in parentheses is the unique  invocation  identifier.    This  is
continuously incrementing regardless of whether or not you are actually seeing
the invocations.  (That is, for as long as Debug Mode is on).  This number can
be  used to cross correlate the trace messages for the various ports, since it
is unique for every invocation.  It will also give an indication of the number
of procedure calls made since the start of  the  execution.    The  invocation
counter  starts  again  for every fresh execution of a command, and it is also
reset when retries (see later) are performed.
                                      14

  The  number  following  this is the current depth.  (ie the number of direct
ancestors this goal has).  This is the number that used to  be  given  by  the
previous Prolog trace mechanism.

  The  next  word  specifies  the  particular port (ie it will be either Call,
Exit, Redo or Fail).

  The goal is then printed so that you can inspect its  current  instantiation
state.  This  is done using 'print(Goal)', which is the evaluable predicate we
discussed earlier.  All goals output by the  tracing  mechanism  can  thus  be
pretty printed if the user desires.

  The  final  '?'  is the prompt indicating that you should type in one of the
option codes allowed (see next section).  If this particular port is unleashed
then you will obviously not get this prompt since you have specified that  you
do not wish to interact at this point.

  I  should  point  out  before  leaving  this  section, that not all calls to
procedures will be traced.  There are a few basic procedures which  have  been
made  invisible  since it is more convenient not to trace them.  These include
all primitive I/O evaluable predicates (eg get, put, read, write),  all  basic
control  structures  (eg  ',',  ';', '->') and all debugging control evaluable
predicates (eg debug, spy, leash, trace).  This means that you will never  see
messages concerning them during debugging.  (This has obvious advantages !).



4. Options available during Debugging
4. Options available during Debugging
4. Options available during Debugging
4. Options available during Debugging
4. Options available during Debugging

  In  this  section I shall describe the particular options that are available
to you when the system prompts you after printing  out  a  debugging  message.
(As mentioned earlier, all trace message go to the terminal.  In a similar way
all the responses are read from the terminal and not from the current input if
it  different.   This allows the debugging of programs during their I/O).  All
the options are simple to learn [sic] one letter mnemonics some of  which  can
be  optionally followed by a decimal integer.  They are read from the terminal
with any blanks being  completely  ignored  up  to  the  next  terminator  (as
described  earlier).   Upper and lower case are considered identical, and some
options only actually require the terminator.  The first basic option which is
the only one you really have to remember is "h" (followed by  RETURN).    This
provides  help  in the form of a list of available options.  I shall reproduce
this list and then describe each entry in turn.


   <CR>  creep                          c    creep
   <LF>  leap                           l    leap
   <ESC> skip                           s    skip
    x    back to choice point           q    quasi skip
    r    retry                          r <nnn>  retry goal nnn
    f    fail                           f <nnn>  fail goal nnn
    a    abort                          e    exit from Prolog
    h    help                           p    print goal
                                      15

    w    write goal                     d    display goal
    g    print ancestor goals           g <nnn>  latest nnn ancestors
    @    accept command                 b    break
    n    nodebug


  The  first  three  options are the basic control decisions that we discussed
earlier.  Notice that as well as single character codes you can also  use  the
three  terminators  directly in order to choose one of these.  This means that
these, most used, options only require single key-strokes.    To  recap  then:
Creep forces the system to single-step to the very next port even if it is not
a  spy-point.  Leap allows the system to proceed until it reaches a spy-point,
and Skip masks out debugging  information  during  the  execution  of  a  goal
reprompting  when  control  returns.   (Skip can only be used at call and redo
ports; control then returns at either  the  exit  or  fail  port  of  the  box
concerned).    Quasi  skip  is  like  skip  except  that  it does not mask out
spy-points.  If there is a spy-point within the execution  of  the  goal  then
control  returns  at  this  point  and any action can be performed there.  The
initial skip still gaurantees an eventual return of control, though, when  the
internal  execution  is  finished.   In this way, quasi-skip is like leap plus
this gaurantee.

  The X option gives you the ability to quickly fail back  to  the  last  real
choice point.  Normally when you creep back through redos and fails you follow
exactly  the  same  path that you followed in order to get where you are.  The
sequence of redos and fails directly mirror  the  original  calls  and  exits.
This  makes it very clear as to where your program is going (if you are unsure
and are trying to learn).  It also makes it possible  for  you  to  reposition
yourself somewhere where you were before in order, say, to trace something you
skipped the last time through.  However, when you know that something is going
to  fail  back  to  some  earlier choicepoint and all you are interested in is
where this point is, then it is rather tedious to follow the execution all the
way back.  The X option is only applicable at fail and redo  ports;  it  keeps
failing  until  either a call port or an exit port is traversed - this will be
just after the choice point.  You cannot interact during  this  time  but  the
system  prints  the  direct  path back from where you started.  This will be a
sequence of fails followed by a sequence of  redos  (either  sequence  may  be
empty).   These are standard debugging messages (as given earlier) except that
the first two characters will be '=>' (and you will not be  prompted).    This
path  represents  the  direct  path  up and down the and/or tree of the proof.
When you are arrive at the call (or exit) port  the  normal  debugging  action
will  occur  - you will be reprompted if the port is leashed or if a spy-point
is set on the procedure.

  The retry and fail options are sophisticated control options which allow you
to really manhandle yourself around the execution  of  your  program.    Retry
means  "Take me back to the call port of this box", this allows you to restart
this invocation when, for example you find yourself leaving  with  some  weird
result.    The  state  of execution is exactly the same as when you originally
called.  (Unless you are misguided enough to use side effects in your  program
-  ie  asserts  etc. will remain).  Retry can be used at any of the four ports
(although at the call port it is a bit of a no-op !).  Fail is similar in that
                                      16

it  means "Take me to the fail port of this box".  This puts your execution in
a position where it is about to backtrack out of this invocation.    I.e.  you
have  manually  failed  the  initial  goal.    Notice  that with both of these
commands you will receive a message for the port you have arrived at,  and  if
this  port  is  leashed,  you will also be reprompted.  (Last chance to change
your mind).  When a retry is performed the invocation counter is reset so that
counting will continue from the current invocation number regardless  of  what
happened  before the retry.  This is in accord with the fact that you have, in
executional terms, returned to the state before anything else was called.    A
message  '[ retry ]'  is output to give a good visual indication of where this
occured in case you are trying to follow these numbers later.

  If you supply an integer after either of the retry or  fail  commands,  then
this  is  taken  as specifying an invocation number and the system trys to get
you to the particular port, not of this box, but of  the  invocation  box  you
have  specified.    It  does this by continuously failing until it reaches the
right place. (Big branch points, like when a procedure has  many  clauses,  or
when  repeat  is used, are cleverly jumped over).  However this process is not
guaranteed !.  (Oh dear).  It may be the case  that  the  invocation  you  are
looking  for  has been completely cut out of the search space by cuts ('!') in
your program (YOU should know).  If this is the case then the system will  end
up going back too far.  When it spots this it will stop.  The result of one of
these big jumps will therefore be either to get you back to the invocation you
wanted  to  get  to, or to the first actually available invocation before this
point.  When the jump is a retry, the invocation  counter  will  be  reset  as
before.    (ie  invocation numbers will start again from the current number of
the box arrived at).  A message '[ ** JUMP ** ]' is  output  to  give  a  good
visual indication of what has occured.

  Abort will cause an abort of this execution.  All the execution states built
so  far  are  destroyed  and  you  are  put right back at the top level of the
interpreter.  (This is the standard Prolog evaluable predicate).

  Exit causes an irreversible exit from the Prolog system back to the  monitor
(Operating System).  (This is the 'halt' evaluable predicate).

  Help displays the table of options given above.

  You  can  also  Re-print,  Write  or  Display  the message you last received
(perhaps your terminal just blew up).  Print will use  'print'  to  print  the
goal  (as  before),  but Write and Display will 'write' and 'display' the goal
(respectively).  This is useful if your pretty print routines (portray)  don't
print  out  the full goal and you suddenly get interested as to what really is
going on down in the depths of some argument.    (Or  maybe  you  have  sudden
doubts  about  the correctness of a pretty print routine ......  well OK maybe
not).

  G provides you with a list of ANCESTORS to the current goal.  That  is,  all
goals that are hierarchically above the current goal in the proof.  This is if
you  like  the procedure nesting, rather than the sequence of invocations that
you would pass through if you kept failing.  The list is printed using 'print'
and each entry is preceded by the invocation number in parentheses followed by
                                      17

the  depth  number.    (This is as would be given in a trace message.)  If the
invocation does not have a number (this will  occur  if  debug  mode  was  not
switched on until further into the execution) then this is marked by '-'.  You
can always be sure of jumping to any goal in the ancestor list (by using retry
etc).    If  you  supply an integer then only that number of ancestors will be
                              last                                            
                              last                                            
                              last                                            
                              last                                            
printed.  That is to say, the last nnn ancestors will be printed counting back
from the current goal.  This option does not really provide the  most  optimal
facility,  but  the ancestor list is currently the only easily available piece
of information.

  The option '@' gives you the ability to call arbitrary Prolog goals.  It  is
a  bit like a quick one-off break.  The initial message '| :- ' will be output
on your terminal, and the prompt is temporarily set to '|    '  (as  described
earlier).  This makes continuation lines look like top level, and this is more
or  less what this is since the term typed in is treated as a goal.  A command
is then read from the terminal and executed.  The execution  of  this  command
will be treated as a separate execution (with invocation numbers starting from
1), and Debug Mode will still be on.  If you wish to single-step you will have
to  use  'trace'  as the first goal as explained earlier.  If you switch Debug
Mode off during this execution it will none-the-less be switched back on  when
you return.  However, any changes to the leashing or to spy-points will remain
in  effect.    This means that you can use this option to switch spy-points on
and off as you progress in your debugging.  Switching spy-points on  will  not
only  trap later calls, but, providing Debug Mode has been on all the time, it
will also trap redo's and fail's further back in the execution should you ever
backtrack back to them.  When the  command  has  been  executed  you  will  be
reprompted  at  the  port  where you issued the '@'.  This re-prompting always
occurs even if you have just switched off the spy-point  you  are  at.    This
gives  you  a free choice of how to continue (if you have just switched it off
then this is the last you will see of it of course!).  These  conventions  are
identical to those for Break.

  Break  will call the normal evaluable predicate 'break', thus putting you at
interpreter top level with the execution so far sitting underneath you.   When
you  end the break (Control-Z) you will be reprompted at the port at which you
broke.  Note that invocation numbers will start again from 1 during the break.
The new execution is completely separate from the suspended one.   Debug  Mode
is not switched off as you call the break, but if you do switch it off then it
will  be  re-switched  on  when  you  finish  the break and go back to the old
execution.  The conventions about spy-point re-setting and  re-prompting  when
the break closes are the same as for the '@' option.

  Finally,  Nodebug  switches Debug Mode off.  You thus lose information about
invocations from here on, although any previous  information  remains  if  you
manage  to  switch  it  on again later (In your program or after a Control-C).
Notice that this is the correct way to switch debugging off at a trace  point.
You  cannot  use the '@' or 'b' options because they always restore Debug Mode
upon return.

  Well that completes this outline of the options available during  debugging.
I  would  suggest  that you just play around with them in order to familiarise
youself with their ins and outs.  They are all quite straight forward really.


                              Table of Contents
                              Table of Contents
                              Table of Contents
                              Table of Contents
                              Table of Contents

1. Control Flow Model                                                        1
2. Control of Debugging Facilities                                           9
3. Format of Debugging messages                                             13
4. Options available during Debugging                                       14